from PyQt5.QtCore import QObject
from PyQt5.QtCore import pyqtSignal, pyqtSlot
import socket
from datetime import datetime
import time
import sys
import json
import os

PATCH_SIZE = 4096  # 4KB
MAX_FILE_LENGTH = 1024 * 1024 * 10 # 10MB
LISTEN_INTERVAL = 200

OTHER_ERR = -1
SOCKET_ERR = 0

TEXT_INVALID = 1
TEXT_SENT    = 2
TEXT_RECV    = 3

IMAGE_INVALID = 'image invalid'
IMAGE_SENT    = 'image sent'
IMAGE_RECV    = 'image recv'
IMAGE_DATA    = 'image data'

        
class ReceiveWorker(QObject):
    textMessageReceived = pyqtSignal(dict)
    imageMessageReceived = pyqtSignal(bytes)
    fileMessageReceived = pyqtSignal(list) # list [fileType:str, fileData:bytes]
    userNamesReceived = pyqtSignal(dict)
    def __init__(self, tcpSocket:socket.socket, ip, port) -> None:
        super().__init__()
        self.tcpSocket = tcpSocket
        self.ip = ip
        self.port = port
        self.nextMsgType = 'text'

    def reconnect(self):
        self.tcpSocket.close()
        print('Reconnecting...')
        try:
            self.tcpSocket.connect((self.ip, self.port))
        except Exception as e:
            print(f"Reconnect error: {e}")
            return False
        else:
            print("TCP reconnected successfully")
            return True

    @pyqtSlot()
    def listenOnce(self):
        if self.nextMsgType == 'text': 
            self.listenText()
        elif self.nextMsgType == 'image':
            self.listenImage()
        elif self.nextMsgType == 'file':
            self.listenFile()


    def listenText(self):
        print("Start listening text")
        data = self.tcpSocket.recv(PATCH_SIZE)
        if data is None or len(data) == 0:
            return
        print(f"Received raw data: {data}")
        try:
            data = data.decode()
            jsonData = json.loads(data)
        except Exception as e: 
            print(f"listen text decode error: {e}")
            return
        if jsonData['type'] == 'text':
            # data = jsonData['data']
            self.textMessageReceived.emit(jsonData)
        elif jsonData['type'] == 'memberList':
            self.userNamesReceived.emit(jsonData)
        elif jsonData['type'] == 'image':
            self.nextMsgType = 'image'
            self.imageSize = jsonData['imageSize']
        elif jsonData['type'] == 'file':
            self.nextMsgType = 'file'
            self.fileSize = jsonData['fileSize']
            self.fileType = jsonData['suffix']
        else:
            print("Unknown message type, skip this message")

    
    def listenImage(self):
        print("Start listening image")
        imageData = b''
        while True:
            if len(imageData) == self.imageSize:
                print('Image data receive completed')
                break
            data = self.tcpSocket.recv(self.imageSize - len(imageData))
            if data is None or len(data) == 0:
                print('Received null data, check what happened')
                break
            imageData += data
    
        print(f'Image data received :{imageData}')
        print(f'Received data size: {len(imageData)}')
        self.nextMsgType = 'text'
        self.imageMessageReceived.emit(imageData)

    
    def listenFile(self):
        print("Start listening file")
        fileData = b''
        while True:
            if len(fileData) == self.fileSize:
                print('File data receive completed')
                break
            data = self.tcpSocket.recv(self.fileSize - len(fileData))
            if data is None or len(data) == 0:
                print('Received null data, check what happened')
                break
            fileData += data
    
        print(f'File data received :{fileData}')
        print(f'Received data size: {len(fileData)}')
        self.nextMsgType = 'text'
        self.fileMessageReceived.emit([self.fileType, fileData])


class SendWorker(QObject):
    textMessageSent = pyqtSignal(int) 
    imageMessageSent = pyqtSignal(str)
    fileMessageSent = pyqtSignal(str)
    newUserNameSent = pyqtSignal()
    def __init__(self, tcpSocket:socket.socket, ip, port) -> None:
        super().__init__()
        self.tcpSocket = tcpSocket
        self.ip = ip
        self.port = port
        
    @pyqtSlot(list)
    def sendTextMessage(self, message:list[str]):
        userName = message[0]
        sendTime = message[1]
        text = message[2]
        print("Start to send text message")
        if len(text) > PATCH_SIZE:
            self.textMessageSent.emit(TEXT_INVALID)
            return
        data = json.dumps({'type':'text','user': userName,'time':sendTime, 'textContent':text})
        self.tcpSocket.send(data.encode())
        self.textMessageSent.emit(TEXT_SENT)
        print(f"Text data sent successfully :{data}")


    @pyqtSlot(list)
    def sendImageMessage(self, message:list[str]):
        userName = message[0]
        sendTime = message[1]
        imagePath = message[2]
        imageSize = os.path.getsize(imagePath)
        if imageSize >= MAX_FILE_LENGTH:
            self.imageMessageSent.emit(IMAGE_INVALID)
            return
        data = json.dumps({'type':'image','user': userName,'time':sendTime,'imageSize': imageSize})
        self.tcpSocket.send(data.encode())
        time.sleep(0.1)
        bytesWritten = 0
        try:
            with open(imagePath, 'rb') as f: # rb: readonly, binary
                data = f.read(MAX_FILE_LENGTH)
                bytesWritten = self.tcpSocket.send(data)
        except Exception as e:
            print(e)
            self.imageMessageSent.emit(str(e))
            return
        else:
            print(f"Image size (bytes):{imageSize}")
            print(f"Bytes successfully written:{bytesWritten}")
            if imageSize == bytesWritten:
                self.imageMessageSent.emit(imagePath)
                print(f"Image data sent successfully: {data}")
            else:
                print('Not all data sent successfully, operation canceled')

    def sendFileMessage(self, message:list[str]):
        userName = message[0]
        sendTime = message[1]
        filePath = message[2]
        fileType = message[3]
        fileSize = os.path.getsize(filePath)
        if fileSize >= MAX_FILE_LENGTH:
            self.imageMessageSent.emit(IMAGE_INVALID)
            return
        data = json.dumps({'type':'file','user': userName,'time':sendTime,'fileSize': fileSize, 'suffix': fileType})
        self.tcpSocket.send(data.encode())
        time.sleep(0.1)
        bytesWritten = 0
        try:
            with open(filePath, 'rb') as f: # rb: readonly, binary
                data = f.read(MAX_FILE_LENGTH)
                bytesWritten = self.tcpSocket.send(data)
        except Exception as e:
            print("error when sending flie data")
            print(e)
            self.fileMessageSent.emit(str(e))
            return
        else:
            print(f"File size (bytes):{fileSize}")
            print(f"Bytes successfully written:{bytesWritten}")
            if fileSize == bytesWritten:
                self.fileMessageSent.emit(filePath)
                print(f"File data sent successfully: {data}")
            else:
                print('Not all data sent successfully, operation canceled')


    @pyqtSlot(list)
    def sendUserName(self, listData:list[str]):
        """
            向服务器发送本客户端的新旧用户名和ip 当初次连接客户端时调用, 更新用户名时也调用
            listData = [ip, oldName, newName]
        """
        ip = listData[0]
        oldName = listData[1]
        newName = listData[2]
        data = json.dumps({'type':'userName','ip':ip, 'oldName': oldName, 'newName': newName})
        self.tcpSocket.send(data.encode())
        self.newUserNameSent.emit()
        print(f"User name sent successfully: {data}")
    